// Copyright (c) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::structure::*;
use std::{
    cell::{Ref, RefCell, RefMut},
    rc::Rc,
};

/// `CdataSection` structure, which corresponds to `Interface CdataSection`.
///
/// # Example
/// ```
/// use ylong_xml::{Document, DocumentMethod};
///
/// let document = Document::new();
/// let cdata_section = document.create_cdata_section("CDATASection").unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct CdataSection {
    pub(crate) inner: Rc<RefCell<CdataSectionInner>>,
}

impl CdataSection {
    /// Create a `CdataSection` with the given value and its owner document.
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CdataSection, Document, ValuedNodeMethod};
    ///
    /// let document = Document::new();
    /// let cdata_section = CdataSection::new("CDATASection", &document).unwrap();
    /// assert_eq!(cdata_section.value(), String::from("CDATASection"));
    /// ```
    pub fn new(value: &str, owner_document: &Document) -> Result<Self, XmlError> {
        Ok(Self {
            inner: Rc::new(RefCell::new(CdataSectionInner::new(
                value.to_string(),
                Some(owner_document.downgrade()),
            ))),
        })
    }

    /// Create an `CdataSection` with child members.
    pub(crate) fn new_unchecked(value: String, owner_document: Option<WeakDocument>) -> Self {
        Self {
            inner: Rc::new(RefCell::new(CdataSectionInner::new(value, owner_document))),
        }
    }
}

impl NodeMethod for CdataSection {
    fn as_node(&self) -> Node {
        Node::CdataSection(self.clone())
    }

    fn name(&self) -> String {
        String::from("#cdata-section")
    }

    fn node_type(&self) -> NodeType {
        NodeType::CdataSection
    }

    fn owner_document(&self) -> Option<Document> {
        self.inner.borrow().owner_document.clone()?.upgrade()
    }
}

impl ValuedNodeMethod for CdataSection {
    fn value(&self) -> String {
        self.inner.borrow().val.clone()
    }

    fn set_value(&mut self, value: &str) {
        self.inner.borrow_mut().val = value.to_string();
    }
}

impl ChildNodeInternalMethod for CdataSection {
    fn set_parent(&mut self, parent: Option<WeakParentNode>) {
        self.inner.borrow_mut().parent = parent;
    }

    fn is_same_child_node(&self, node: &ChildNode) -> bool {
        node.as_cdata_section().map(|n| n == self).unwrap_or(false)
    }
}

impl ChildNodeMethod for CdataSection {
    fn as_child_node(&self) -> ChildNode {
        ChildNode::CdataSection(self.clone())
    }

    fn parent(&self) -> Option<ParentNode> {
        self.inner.borrow().parent.clone()?.upgrade()
    }
}

impl CharacterDataBorrowMethod for CdataSection {
    type Inner = CdataSectionInner;

    fn borrow(&self) -> Ref<'_, Self::Inner> {
        self.inner.borrow()
    }

    fn borrow_mut(&self) -> RefMut<'_, Self::Inner> {
        self.inner.borrow_mut()
    }
}

impl PartialEq for CdataSection {
    fn eq(&self, other: &Self) -> bool {
        self.inner.as_ptr() == other.inner.as_ptr()
    }
}

#[derive(Debug)]
pub(crate) struct CdataSectionInner {
    pub(crate) val: String,
    pub(crate) parent: Option<WeakParentNode>,
    pub(crate) owner_document: Option<WeakDocument>,
}

impl CdataSectionInner {
    pub(crate) fn new(value: String, owner_document: Option<WeakDocument>) -> Self {
        Self {
            val: value,
            parent: None,
            owner_document,
        }
    }
}

impl CharacterDataInternalMethod for CdataSectionInner {
    fn get_data(&self) -> &String {
        &self.val
    }

    fn get_data_mut(&mut self) -> &mut String {
        &mut self.val
    }
}

#[cfg(test)]
mod ut_cdata_section {
    use crate::structure::*;

    /*
     * @title  UT test for cdata borrow function
     * @design Condition Coverage
     * @precon NA
     * @brief  1. Call `new()` method to create a cdata_section.
     *         2. Call borrow get data。
     *         3. Check if borrow val is "CDATASection"
     * @expect 1. true。
     * @auto   yes
     */
    #[test]
    fn ut_cdata_section_borrow() {
        let document = Document::new();
        let cdata_section = CdataSection::new("CDATASection", &document).unwrap();
        let cdata_section_borrow = cdata_section.borrow();
        let borrow_val = cdata_section_borrow.get_data();
        assert_eq!(borrow_val, "CDATASection");

        let cdata_section = CdataSection::new("CDATASection", &document).unwrap();
        let mut cdata_section_borrow_mut = cdata_section.borrow_mut();
        let borrow_mut_val = &*cdata_section_borrow_mut.get_data_mut();
        assert_eq!(borrow_mut_val, "CDATASection");
    }
    /// UT test case for `CdataSection::new()`.
    ///
    /// # Brief
    /// 1. Call `new()` method to create a `CdataSection`.
    /// 2. Check if parts of `CdataSection` are default values.
    #[test]
    fn ut_cdata_section_new() {
        let document = Document::new();
        let cdata_section = CdataSection::new("CDATASection", &document).unwrap();
        let cdata_section_inner = cdata_section.inner.borrow();
        assert_eq!(cdata_section_inner.val, "CDATASection");
        assert_eq!(cdata_section_inner.parent, None);
        assert_eq!(
            cdata_section_inner.owner_document,
            Some(document.downgrade())
        );
    }

    /// UT test case for `CdataSection::as_node()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section`.
    /// 2. Call `as_node()` method to convert `cdata_section` to `Node`.
    /// 3. Check if `Node::CdataSection(cdata_section)` is obtained.
    #[test]
    fn ut_cdata_section_as_node() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        let node = cdata_section.as_node();
        assert_eq!(node, Node::CdataSection(cdata_section));
    }

    /// UT test case for `CdataSection::name()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section`.
    /// 2. Call `name()` method to obtain `cdata_section`'s name.
    /// 3. Check if the obtained name is the given name.
    #[test]
    fn ut_cdata_section_name() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        assert_eq!(cdata_section.name(), "#cdata-section");
    }

    /// UT test case for `CdataSection::node_type()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section`.
    /// 2. Call `node_type()` method to obtain `cdata_section`'s type.
    /// 3. Check if this obtained node type is `NodeType::CdataSection`.
    #[test]
    fn ut_cdata_section_node_type() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        assert_eq!(cdata_section.node_type(), NodeType::CdataSection);
    }

    /// UT test case for `CdataSection::owner_document()`.
    ///
    /// # Brief
    /// 1. Create a `Document` called `document`.
    /// 2. Create an `CdataSection` of `document`, which is called `cdata_section`.
    /// 3. Call `owner_document()` method to obtain `cdata_section`'s owner document.
    /// 4. Check if `cdata_section`'s owner document is `Some(document)`.
    #[test]
    fn ut_cdata_section_owner_document() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        assert_eq!(cdata_section.owner_document(), Some(document));
    }

    /// UT test case for `CdataSection::value()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section` whose value is set to `"CDATASection"`.
    /// 2. Call `value()` method to obtain `cdata_section`'s value.
    /// 3. Check if the value is equal to `"CDATASection"`.
    #[test]
    fn ut_cdata_section_value() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        assert_eq!(cdata_section.value(), "CDATASection");
    }

    /// UT test case for `CdataSection::set_value()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section` whose value is set to `"OldCDATASection"`.
    /// 2. Call `set_value()` method to set the value of `cdata_section` to `"NewCDATASection"`.
    /// 3. Get the value of `cdata_section` and check if it is equal to `"NewCDATASection"`.
    #[test]
    fn ut_cdata_section_set_value() {
        let document = Document::new();
        let mut cdata_section = document.create_cdata_section("OldCDATASection").unwrap();
        cdata_section.set_value("NewCDATASection");
        assert_eq!(cdata_section.value(), "NewCDATASection");
    }

    /// UT test case for `CdataSection::set_parent()`.
    ///
    /// # Brief
    /// 1. Create a `Document` called `document`.
    /// 2. Create a `CdataSection` called `cdata_section`.
    /// 3. Call `set_parent()` method to set `cdata_section`'s parent to `document`.
    /// 4. Get the parent of `cdata_section` and check if it is the same as `document`.
    #[test]
    fn ut_cdata_section_set_parent() {
        let document = Document::new();
        let mut cdata_section = document.create_cdata_section("CDATASection").unwrap();
        cdata_section.set_parent(Some(document.as_parent_node().downgrade()));
        assert_eq!(cdata_section.parent(), Some(document.as_parent_node()));
    }

    /// UT test case for `CdataSection::is_same_child_node()`.
    ///
    /// # Brief
    /// 1. Create two `CdataSection`s called `cdata_section1` and `cdata_section2`.
    /// 2. Call `as_node()` method to convert these two `CdataSection`s into
    /// `child1` and `child2` respectively.
    /// 3. Call `is_same_child_node` method to check if `cdata_section1` is the
    /// same as `child1`.
    /// 4. Call `is_same_child_node` method to check if `cdata_section1` is the
    /// same as `child2`.
    #[test]
    fn ut_cdata_section_is_same_child_node() {
        let document = Document::new();
        let cdata_section1 = document.create_cdata_section("CDATASection").unwrap();
        let child1 = cdata_section1.as_child_node();
        let cdata_section2 = document.create_cdata_section("CDATASection").unwrap();
        let child2 = cdata_section2.as_child_node();

        assert!(cdata_section1.is_same_child_node(&child1));
        assert!(!cdata_section1.is_same_child_node(&child2));
    }

    /// UT test case for `CdataSection::as_child_node()`.
    ///
    /// # Brief
    /// 1. Create a `CdataSection` called `cdata_section`.
    /// 2. Call `as_child_node()` method to convert `cdata_section` to `ChildNode`.
    /// 3. Check if `ChildNode::Cdata(comment)` is obtained.
    #[test]
    fn ut_cdata_section_as_child_node() {
        let document = Document::new();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        let child_node = cdata_section.as_child_node();
        assert_eq!(child_node, ChildNode::CdataSection(cdata_section));
    }

    /// UT test case for `CdataSection::parent()`.
    ///
    /// # Brief
    /// 1. Create a `Document` called `document`.
    /// 2. Create a `Comment` called `comment`.
    /// 3. Call `parent()` method to check if `comment` has no parent.
    /// 4. Set `comment`'s parent to `document`.
    /// 5. Call `parent()` method to check if `comment`'s parent is `document`.
    #[test]
    fn ut_cdata_section_parent() {
        let document = Document::new();
        let mut element = document.create_element("Element").unwrap();
        let cdata_section = document.create_cdata_section("CDATASection").unwrap();
        assert_eq!(cdata_section.parent(), None);

        let _ = element.append_child(cdata_section.clone());
        assert_eq!(cdata_section.parent(), Some(element.as_parent_node()));
    }
}
